Merge "localisation: Improve documentation around wgLocalisationCacheConf"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 7 May 2019 23:03:41 +0000 (23:03 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 7 May 2019 23:03:41 +0000 (23:03 +0000)
15 files changed:
includes/Block.php
includes/api/ApiBase.php
includes/api/ApiBlockInfoTrait.php
includes/block/AbstractBlock.php
includes/block/BlockManager.php
includes/block/SystemBlock.php [new file with mode: 0644]
includes/exception/UserBlockedError.php
includes/user/User.php
tests/phpunit/includes/BlockTest.php
tests/phpunit/includes/Permissions/PermissionManagerTest.php
tests/phpunit/includes/SystemBlockTest.php [new file with mode: 0644]
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/api/ApiBlockInfoTraitTest.php
tests/phpunit/includes/user/PasswordResetTest.php
tests/phpunit/includes/user/UserTest.php

index 6bfd7d3..472c36e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Blocks and bans object
+ * Class for blocks stored in the database.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -29,22 +29,16 @@ use MediaWiki\Block\Restriction\NamespaceRestriction;
 use MediaWiki\Block\Restriction\PageRestriction;
 use MediaWiki\MediaWikiServices;
 
+/**
+ * Blocks (as opposed to system blocks) are stored in the database, may
+ * give rise to autoblocks and may be tracked with cookies. Blocks are
+ * more customizable than system blocks: they may be hardblocks, and
+ * they may be sitewide or partial.
+ */
 class Block extends AbstractBlock {
-       /** @var string */
-       public $mReason;
-
-       /** @var string */
-       public $mTimestamp;
-
        /** @var bool */
        public $mAuto;
 
-       /** @var string */
-       public $mExpiry;
-
-       /** @var bool */
-       public $mHideName;
-
        /** @var int */
        public $mParentBlockId;
 
@@ -54,61 +48,23 @@ class Block extends AbstractBlock {
        /** @var bool */
        private $mFromMaster;
 
-       /** @var bool */
-       private $mBlockEmail;
-
-       /** @var bool */
-       private $allowUsertalk;
-
-       /** @var bool */
-       private $blockCreateAccount;
-
-       /** @var User|string */
-       private $target;
-
        /** @var int Hack for foreign blocking (CentralAuth) */
        private $forcedTargetID;
 
-       /**
-        * @var int Block::TYPE_ constant. After the block has been loaded
-        * from the database, this can only be USER, IP or RANGE.
-        */
-       private $type;
-
-       /** @var User */
-       private $blocker;
-
        /** @var bool */
        private $isHardblock;
 
        /** @var bool */
        private $isAutoblocking;
 
-       /** @var string|null */
-       private $systemBlockType;
-
-       /** @var bool */
-       private $isSitewide;
-
        /** @var Restriction[] */
        private $restrictions;
 
-       # TYPE constants
-       const TYPE_USER = 1;
-       const TYPE_IP = 2;
-       const TYPE_RANGE = 3;
-       const TYPE_AUTO = 4;
-       const TYPE_ID = 5;
-
        /**
         * Create a new block with specified option parameters on a user, IP or IP range.
         *
         * @param array $options Parameters of the block:
-        *     address string|User  Target user name, User object, IP address or IP range
         *     user int             Override target user ID (for foreign users)
-        *     by int               User ID of the blocker
-        *     reason string        Reason of the block
-        *     timestamp string     The time at which the block comes into effect
         *     auto bool            Is this an automatic block?
         *     expiry string        Timestamp of expiration of the block or 'infinity'
         *     anonOnly bool        Only disallow anonymous actions
@@ -117,11 +73,6 @@ class Block extends AbstractBlock {
         *     hideName bool        Hide the target user name
         *     blockEmail bool      Disallow sending emails
         *     allowUsertalk bool   Allow the target to edit its own talk page
-        *     byText string        Username of the blocker (for foreign users)
-        *     systemBlock string   Indicate that this block is automatically
-        *                          created by MediaWiki rather than being stored
-        *                          in the database. Value is a string to return
-        *                          from self::getSystemBlockType().
         *     sitewide bool        Disallow editing all pages and all contribution
         *                          actions, except those specifically allowed by
         *                          other block flags
@@ -129,12 +80,10 @@ class Block extends AbstractBlock {
         * @since 1.26 $options array
         */
        public function __construct( array $options = [] ) {
+               parent::__construct( $options );
+
                $defaults = [
-                       'address'         => '',
                        'user'            => null,
-                       'by'              => null,
-                       'reason'          => '',
-                       'timestamp'       => '',
                        'auto'            => false,
                        'expiry'          => '',
                        'anonOnly'        => false,
@@ -143,30 +92,16 @@ class Block extends AbstractBlock {
                        'hideName'        => false,
                        'blockEmail'      => false,
                        'allowUsertalk'   => false,
-                       'byText'          => '',
-                       'systemBlock'     => null,
                        'sitewide'        => true,
                ];
 
                $options += $defaults;
 
-               $this->setTarget( $options['address'] );
-
                if ( $this->target instanceof User && $options['user'] ) {
                        # Needed for foreign users
                        $this->forcedTargetID = $options['user'];
                }
 
-               if ( $options['by'] ) {
-                       # Local user
-                       $this->setBlocker( User::newFromId( $options['by'] ) );
-               } else {
-                       # Foreign user
-                       $this->setBlocker( $options['byText'] );
-               }
-
-               $this->setReason( $options['reason'] );
-               $this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
                $this->setExpiry( wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] ) );
 
                # Boolean settings
@@ -180,7 +115,6 @@ class Block extends AbstractBlock {
                $this->isUsertalkEditAllowed( (bool)$options['allowUsertalk'] );
 
                $this->mFromMaster = false;
-               $this->systemBlockType = $options['systemBlock'];
        }
 
        /**
@@ -545,9 +479,6 @@ class Block extends AbstractBlock {
        public function insert( $dbw = null ) {
                global $wgBlockDisablesLogin;
 
-               if ( $this->getSystemBlockType() !== null ) {
-                       throw new MWException( 'Cannot insert a system block into the database' );
-               }
                if ( !$this->getBlocker() || $this->getBlocker()->getName() === '' ) {
                        throw new MWException( 'Cannot insert a block without a blocker set' );
                }
@@ -861,11 +792,6 @@ class Block extends AbstractBlock {
                        return false;
                }
 
-               # Don't autoblock for system blocks
-               if ( $this->getSystemBlockType() !== null ) {
-                       throw new MWException( 'Cannot autoblock from a system block' );
-               }
-
                # Check for presence on the autoblock whitelist.
                if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
                        return false;
@@ -1036,26 +962,7 @@ class Block extends AbstractBlock {
        }
 
        /**
-        * Get the user id of the blocking sysop
-        *
-        * @return int (0 for foreign users)
-        */
-       public function getBy() {
-               return $this->getBlocker()->getId();
-       }
-
-       /**
-        * Get the username of the blocking sysop
-        *
-        * @return string
-        */
-       public function getByName() {
-               return $this->getBlocker()->getName();
-       }
-
-       /**
-        * Get the block ID
-        * @return int
+        * @inheritDoc
         */
        public function getId() {
                return $this->mId;
@@ -1079,55 +986,6 @@ class Block extends AbstractBlock {
                return $this;
        }
 
-       /**
-        * Get the reason given for creating the block
-        *
-        * @since 1.33
-        * @return string
-        */
-       public function getReason() {
-               return $this->mReason;
-       }
-
-       /**
-        * Set the reason for creating the block
-        *
-        * @since 1.33
-        * @param string $reason
-        */
-       public function setReason( $reason ) {
-               $this->mReason = $reason;
-       }
-
-       /**
-        * Get whether the block hides the target's username
-        *
-        * @since 1.33
-        * @return bool The block hides the username
-        */
-       public function getHideName() {
-               return $this->mHideName;
-       }
-
-       /**
-        * Set whether ths block hides the target's username
-        *
-        * @since 1.33
-        * @param bool $hideName The block hides the username
-        */
-       public function setHideName( $hideName ) {
-               $this->mHideName = $hideName;
-       }
-
-       /**
-        * Get the system block type, if any
-        * @since 1.29
-        * @return string|null
-        */
-       public function getSystemBlockType() {
-               return $this->systemBlockType;
-       }
-
        /**
         * Get/set a flag determining whether the master is used for reads
         *
@@ -1166,165 +1024,6 @@ class Block extends AbstractBlock {
                        : false;
        }
 
-       /**
-        * Indicates that the block is a sitewide block. This means the user is
-        * prohibited from editing any page on the site (other than their own talk
-        * page).
-        *
-        * @since 1.33
-        * @param null|bool $x
-        * @return bool
-        */
-       public function isSitewide( $x = null ) {
-               return wfSetVar( $this->isSitewide, $x );
-       }
-
-       /**
-        * Get or set the flag indicating whether this block blocks the target from
-        * creating an account. (Note that the flag may be overridden depending on
-        * global configs.)
-        *
-        * @since 1.33
-        * @param null|bool $x Value to set (if null, just get the property value)
-        * @return bool Value of the property
-        */
-       public function isCreateAccountBlocked( $x = null ) {
-               return wfSetVar( $this->blockCreateAccount, $x );
-       }
-
-       /**
-        * Get or set the flag indicating whether this block blocks the target from
-        * sending emails. (Note that the flag may be overridden depending on
-        * global configs.)
-        *
-        * @since 1.33
-        * @param null|bool $x Value to set (if null, just get the property value)
-        * @return bool Value of the property
-        */
-       public function isEmailBlocked( $x = null ) {
-               return wfSetVar( $this->mBlockEmail, $x );
-       }
-
-       /**
-        * Get or set the flag indicating whether this block blocks the target from
-        * editing their own user talk page. (Note that the flag may be overridden
-        * depending on global configs.)
-        *
-        * @since 1.33
-        * @param null|bool $x Value to set (if null, just get the property value)
-        * @return bool Value of the property
-        */
-       public function isUsertalkEditAllowed( $x = null ) {
-               return wfSetVar( $this->allowUsertalk, $x );
-       }
-
-       /**
-        * Determine whether the Block prevents a given right. A right
-        * may be blacklisted or whitelisted, or determined from a
-        * property on the Block object. For certain rights, the property
-        * may be overridden according to global configs.
-        *
-        * @since 1.33
-        * @param string $right Right to check
-        * @return bool|null null if unrecognized right or unset property
-        */
-       public function appliesToRight( $right ) {
-               $config = RequestContext::getMain()->getConfig();
-               $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
-
-               $res = null;
-               switch ( $right ) {
-                       case 'edit':
-                               // TODO: fix this case to return proper value
-                               $res = true;
-                               break;
-                       case 'createaccount':
-                               $res = $this->isCreateAccountBlocked();
-                               break;
-                       case 'sendemail':
-                               $res = $this->isEmailBlocked();
-                               break;
-                       case 'upload':
-                               // Until T6995 is completed
-                               $res = $this->isSitewide();
-                               break;
-                       case 'read':
-                               $res = false;
-                               break;
-                       case 'purge':
-                               $res = false;
-                               break;
-               }
-               if ( !$res && $blockDisablesLogin ) {
-                       // If a block would disable login, then it should
-                       // prevent any right that all users cannot do
-                       $anon = new User;
-                       $res = $anon->isAllowed( $right ) ? $res : true;
-               }
-
-               return $res;
-       }
-
-       /**
-        * Get/set whether the Block prevents a given action
-        *
-        * @deprecated since 1.33, use appliesToRight to determine block
-        *  behaviour, and specific methods to get/set properties
-        * @param string $action Action to check
-        * @param bool|null $x Value for set, or null to just get value
-        * @return bool|null Null for unrecognized rights.
-        */
-       public function prevents( $action, $x = null ) {
-               $config = RequestContext::getMain()->getConfig();
-               $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
-               $blockAllowsUTEdit = $config->get( 'BlockAllowsUTEdit' );
-
-               $res = null;
-               switch ( $action ) {
-                       case 'edit':
-                               # For now... <evil laugh>
-                               $res = true;
-                               break;
-                       case 'createaccount':
-                               $res = wfSetVar( $this->blockCreateAccount, $x );
-                               break;
-                       case 'sendemail':
-                               $res = wfSetVar( $this->mBlockEmail, $x );
-                               break;
-                       case 'upload':
-                               // Until T6995 is completed
-                               $res = $this->isSitewide();
-                               break;
-                       case 'editownusertalk':
-                               // NOTE: this check is not reliable on partial blocks
-                               // since partially blocked users are always allowed to edit
-                               // their own talk page unless a restriction exists on the
-                               // page or User_talk: namespace
-                               wfSetVar( $this->allowUsertalk, $x === null ? null : !$x );
-                               $res = !$this->isUsertalkEditAllowed();
-
-                               // edit own user talk can be disabled by config
-                               if ( !$blockAllowsUTEdit ) {
-                                       $res = true;
-                               }
-                               break;
-                       case 'read':
-                               $res = false;
-                               break;
-                       case 'purge':
-                               $res = false;
-                               break;
-               }
-               if ( !$res && $blockDisablesLogin ) {
-                       // If a block would disable login, then it should
-                       // prevent any action that all users cannot do
-                       $anon = new User;
-                       $res = $anon->isAllowed( $action ) ? $res : true;
-               }
-
-               return $res;
-       }
-
        /**
         * Get the block name, but with autoblocked IPs hidden as per standard privacy policy
         * @return string Text is escaped
@@ -1614,170 +1313,15 @@ class Block extends AbstractBlock {
        }
 
        /**
-        * From an existing Block, get the target and the type of target.
-        * Note that, except for null, it is always safe to treat the target
-        * as a string; for User objects this will return User::__toString()
-        * which in turn gives User::getName().
+        * @inheritDoc
         *
-        * @param string|int|User|null $target
-        * @return array [ User|String|null, Block::TYPE_ constant|null ]
-        */
-       public static function parseTarget( $target ) {
-               # We may have been through this before
-               if ( $target instanceof User ) {
-                       if ( IP::isValid( $target->getName() ) ) {
-                               return [ $target, self::TYPE_IP ];
-                       } else {
-                               return [ $target, self::TYPE_USER ];
-                       }
-               } elseif ( $target === null ) {
-                       return [ null, null ];
-               }
-
-               $target = trim( $target );
-
-               if ( IP::isValid( $target ) ) {
-                       # We can still create a User if it's an IP address, but we need to turn
-                       # off validation checking (which would exclude IP addresses)
-                       return [
-                               User::newFromName( IP::sanitizeIP( $target ), false ),
-                               self::TYPE_IP
-                       ];
-
-               } elseif ( IP::isValidRange( $target ) ) {
-                       # Can't create a User from an IP range
-                       return [ IP::sanitizeRange( $target ), self::TYPE_RANGE ];
-               }
-
-               # Consider the possibility that this is not a username at all
-               # but actually an old subpage (T31797)
-               if ( strpos( $target, '/' ) !== false ) {
-                       # An old subpage, drill down to the user behind it
-                       $target = explode( '/', $target )[0];
-               }
-
-               $userObj = User::newFromName( $target );
-               if ( $userObj instanceof User ) {
-                       # Note that since numbers are valid usernames, a $target of "12345" will be
-                       # considered a User.  If you want to pass a block ID, prepend a hash "#12345",
-                       # since hash characters are not valid in usernames or titles generally.
-                       return [ $userObj, self::TYPE_USER ];
-
-               } elseif ( preg_match( '/^#\d+$/', $target ) ) {
-                       # Autoblock reference in the form "#12345"
-                       return [ substr( $target, 1 ), self::TYPE_AUTO ];
-
-               } else {
-                       # WTF?
-                       return [ null, null ];
-               }
-       }
-
-       /**
-        * Get the type of target for this particular block. Autoblocks have whichever type
-        * corresponds to their target, so to detect if a block is an autoblock, we have to
-        * check the mAuto property instead.
-        * @return int Block::TYPE_ constant, will never be TYPE_ID
+        * Autoblocks have whichever type corresponds to their target, so to detect if a block is an
+        * autoblock, we have to check the mAuto property instead.
         */
        public function getType() {
                return $this->mAuto
                        ? self::TYPE_AUTO
-                       : $this->type;
-       }
-
-       /**
-        * Get the target and target type for this particular Block.  Note that for autoblocks,
-        * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
-        * in this situation.
-        * @return array [ User|String, Block::TYPE_ constant ]
-        * @todo FIXME: This should be an integral part of the Block member variables
-        */
-       public function getTargetAndType() {
-               return [ $this->getTarget(), $this->getType() ];
-       }
-
-       /**
-        * Get the target for this particular Block.  Note that for autoblocks,
-        * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
-        * in this situation.
-        * @return User|string
-        */
-       public function getTarget() {
-               return $this->target;
-       }
-
-       /**
-        * Get the block expiry time
-        *
-        * @since 1.19
-        * @return string
-        */
-       public function getExpiry() {
-               return $this->mExpiry;
-       }
-
-       /**
-        * Set the block expiry time
-        *
-        * @since 1.33
-        * @param string $expiry
-        */
-       public function setExpiry( $expiry ) {
-               $this->mExpiry = $expiry;
-       }
-
-       /**
-        * Get the timestamp indicating when the block was created
-        *
-        * @since 1.33
-        * @return string
-        */
-       public function getTimestamp() {
-               return $this->mTimestamp;
-       }
-
-       /**
-        * Set the timestamp indicating when the block was created
-        *
-        * @since 1.33
-        * @param string $timestamp
-        */
-       public function setTimestamp( $timestamp ) {
-               $this->mTimestamp = $timestamp;
-       }
-
-       /**
-        * Set the target for this block, and update $this->type accordingly
-        * @param mixed $target
-        */
-       public function setTarget( $target ) {
-               list( $this->target, $this->type ) = self::parseTarget( $target );
-       }
-
-       /**
-        * Get the user who implemented this block
-        * @return User User object. May name a foreign user.
-        */
-       public function getBlocker() {
-               return $this->blocker;
-       }
-
-       /**
-        * Set the user who implemented (or will implement) this block
-        * @param User|string $user Local User object or username string
-        */
-       public function setBlocker( $user ) {
-               if ( is_string( $user ) ) {
-                       $user = User::newFromName( $user, false );
-               }
-
-               if ( $user->isAnon() && User::isUsableName( $user->getName() ) ) {
-                       throw new InvalidArgumentException(
-                               'Blocker must be a local user or a name that cannot be a local user'
-                       );
-               }
-
-               $this->blocker = $user;
+                       : parent::getType();
        }
 
        /**
@@ -1869,19 +1413,15 @@ class Block extends AbstractBlock {
        }
 
        /**
-        * Get the key and parameters for the corresponding error message.
+        * @inheritDoc
         *
-        * @since 1.22
-        * @param IContextSource $context
-        * @return array
+        * Build different messages for autoblocks and partial blocks.
         */
        public function getPermissionsError( IContextSource $context ) {
                $params = $this->getBlockErrorParams( $context );
 
                $msg = 'blockedtext';
-               if ( $this->getSystemBlockType() !== null ) {
-                       $msg = 'systemblockedtext';
-               } elseif ( $this->mAuto ) {
+               if ( $this->mAuto ) {
                        $msg = 'autoblockedtext';
                } elseif ( !$this->isSitewide() ) {
                        $msg = 'blockedtext-partial';
@@ -1892,45 +1432,6 @@ class Block extends AbstractBlock {
                return $params;
        }
 
-       /**
-        * Get block information used in different block error messages
-        *
-        * @since 1.33
-        * @param IContextSource $context
-        * @return array
-        */
-       public function getBlockErrorParams( IContextSource $context ) {
-               $blocker = $this->getBlocker();
-               if ( $blocker instanceof User ) { // local user
-                       $blockerUserpage = $blocker->getUserPage();
-                       $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
-               } else { // foreign user
-                       $link = $blocker;
-               }
-
-               $reason = $this->getReason();
-               if ( $reason == '' ) {
-                       $reason = $context->msg( 'blockednoreason' )->text();
-               }
-
-               /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
-                * This could be a username, an IP range, or a single IP. */
-               $intended = $this->getTarget();
-               $systemBlockType = $this->getSystemBlockType();
-               $lang = $context->getLanguage();
-
-               return [
-                       $link,
-                       $reason,
-                       $context->getRequest()->getIP(),
-                       $this->getByName(),
-                       $systemBlockType ?? $this->getId(),
-                       $lang->formatExpiry( $this->getExpiry() ),
-                       (string)$intended,
-                       $lang->userTimeAndDate( $this->getTimestamp(), $context->getUser() ),
-               ];
-       }
-
        /**
         * Get Restrictions.
         *
@@ -1969,76 +1470,7 @@ class Block extends AbstractBlock {
        }
 
        /**
-        * Determine whether the block allows the user to edit their own
-        * user talk page. This is done separately from Block::appliesToRight
-        * because there is no right for editing one's own user talk page
-        * and because the user's talk page needs to be passed into the
-        * Block object, which is unaware of the user.
-        *
-        * The ipb_allow_usertalk flag (which corresponds to the property
-        * allowUsertalk) is used on sitewide blocks and partial blocks
-        * that contain a namespace restriction on the user talk namespace,
-        * but do not contain a page restriction on the user's talk page.
-        * For all other (i.e. most) partial blocks, the flag is ignored,
-        * and the user can always edit their user talk page unless there
-        * is a page restriction on their user talk page, in which case
-        * they can never edit it. (Ideally the flag would be stored as
-        * null in these cases, but the database field isn't nullable.)
-        *
-        * This method does not validate that the passed in talk page belongs to the
-        * block target since the target (an IP) might not be the same as the user's
-        * talk page (if they are logged in).
-        *
-        * @since 1.33
-        * @param Title|null $usertalk The user's user talk page. If null,
-        *  and if the target is a User, the target's userpage is used
-        * @return bool The user can edit their talk page
-        */
-       public function appliesToUsertalk( Title $usertalk = null ) {
-               if ( !$usertalk ) {
-                       if ( $this->target instanceof User ) {
-                               $usertalk = $this->target->getTalkPage();
-                       } else {
-                               throw new InvalidArgumentException(
-                                       '$usertalk must be provided if block target is not a user/IP'
-                               );
-                       }
-               }
-
-               if ( $usertalk->getNamespace() !== NS_USER_TALK ) {
-                       throw new InvalidArgumentException(
-                               '$usertalk must be a user talk page'
-                       );
-               }
-
-               if ( !$this->isSitewide() ) {
-                       if ( $this->appliesToPage( $usertalk->getArticleID() ) ) {
-                               return true;
-                       }
-                       if ( !$this->appliesToNamespace( NS_USER_TALK ) ) {
-                               return false;
-                       }
-               }
-
-               // This is a type of block which uses the ipb_allow_usertalk
-               // flag. The flag can still be overridden by global configs.
-               $config = RequestContext::getMain()->getConfig();
-               if ( !$config->get( 'BlockAllowsUTEdit' ) ) {
-                       return true;
-               }
-               return !$this->isUsertalkEditAllowed();
-       }
-
-       /**
-        * Checks if a block applies to a particular title
-        *
-        * This check does not consider whether `$this->isUsertalkEditAllowed`
-        * returns false, as the identity of the user making the hypothetical edit
-        * isn't known here (particularly in the case of IP hardblocks, range
-        * blocks, and auto-blocks).
-        *
-        * @param Title $title
-        * @return bool
+        * @inheritDoc
         */
        public function appliesToTitle( Title $title ) {
                if ( $this->isSitewide() ) {
@@ -2056,12 +1488,7 @@ class Block extends AbstractBlock {
        }
 
        /**
-        * Checks if a block applies to a particular namespace
-        *
-        * @since 1.33
-        *
-        * @param int $ns
-        * @return bool
+        * @inheritDoc
         */
        public function appliesToNamespace( $ns ) {
                if ( $this->isSitewide() ) {
@@ -2079,17 +1506,7 @@ class Block extends AbstractBlock {
        }
 
        /**
-        * Checks if a block applies to a particular page
-        *
-        * This check does not consider whether `$this->isUsertalkEditAllowed`
-        * returns false, as the identity of the user making the hypothetical edit
-        * isn't known here (particularly in the case of IP hardblocks, range
-        * blocks, and auto-blocks).
-        *
-        * @since 1.33
-        *
-        * @param int $pageId
-        * @return bool
+        * @inheritDoc
         */
        public function appliesToPage( $pageId ) {
                if ( $this->isSitewide() ) {
@@ -2129,11 +1546,7 @@ class Block extends AbstractBlock {
        }
 
        /**
-        * Check if the block should be tracked with a cookie.
-        *
-        * @since 1.33
-        * @param bool $isAnon The user is logged out
-        * @return bool The block should be tracked with a cookie
+        * @inheritDoc
         */
        public function shouldTrackWithCookie( $isAnon ) {
                $config = RequestContext::getMain()->getConfig();
@@ -2148,27 +1561,6 @@ class Block extends AbstractBlock {
                }
        }
 
-       /**
-        * Check if the block prevents a user from resetting their password
-        *
-        * @since 1.33
-        * @return bool The block blocks password reset
-        */
-       public function appliesToPasswordReset() {
-               switch ( $this->getSystemBlockType() ) {
-                       case null:
-                       case 'global-block':
-                               return $this->isCreateAccountBlocked();
-                       case 'proxy':
-                               return true;
-                       case 'dnsbl':
-                       case 'wgSoftBlockRanges':
-                               return false;
-                       default:
-                               return true;
-               }
-       }
-
        /**
         * Get a BlockRestrictionStore instance
         *
index dbf72be..bc62906 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  */
 
+use MediaWiki\Block\AbstractBlock;
 use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\IDatabase;
 
@@ -2031,7 +2032,7 @@ abstract class ApiBase extends ContextSource {
         * @param Block $block The block used to generate the ApiUsageException
         * @throws ApiUsageException always
         */
-       public function dieBlocked( Block $block ) {
+       public function dieBlocked( AbstractBlock $block ) {
                // Die using the appropriate message depending on block type
                if ( $block->getType() == Block::TYPE_AUTO ) {
                        $this->dieWithError(
index 2663485..51da835 100644 (file)
@@ -18,6 +18,9 @@
  * @file
  */
 
+use MediaWiki\Block\AbstractBlock;
+use MediaWiki\Block\SystemBlock;
+
 /**
  * @ingroup API
  */
@@ -35,7 +38,7 @@ trait ApiBlockInfoTrait {
         *  - blockexpiry - expiry time of the block
         *  - systemblocktype - system block type, if any
         */
-       private function getBlockInfo( Block $block ) {
+       private function getBlockInfo( AbstractBlock $block ) {
                $vals = [];
                $vals['blockid'] = $block->getId();
                $vals['blockedby'] = $block->getByName();
@@ -44,7 +47,7 @@ trait ApiBlockInfoTrait {
                $vals['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $block->getTimestamp() );
                $vals['blockexpiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' );
                $vals['blockpartial'] = !$block->isSitewide();
-               if ( $block->getSystemBlockType() !== null ) {
+               if ( $block instanceof SystemBlock ) {
                        $vals['systemblocktype'] = $block->getSystemBlockType();
                }
                return $vals;
index f432440..a931d7a 100644 (file)
 
 namespace MediaWiki\Block;
 
+use IContextSource;
+use InvalidArgumentException;
+use IP;
+use RequestContext;
+use Title;
+use User;
+
 /**
- * @since 1.34
+ * @since 1.34 Factored out from Block.
  */
 abstract class AbstractBlock {
+       /** @var string */
+       public $mReason;
+
+       /** @var string */
+       public $mTimestamp;
+
+       /** @var string */
+       public $mExpiry = '';
+
+       /** @var bool */
+       protected $mBlockEmail = false;
+
+       /** @var bool */
+       protected $allowUsertalk = false;
+
+       /** @var bool */
+       protected $blockCreateAccount = false;
+
+       /** @var bool */
+       public $mHideName = false;
+
+       /** @var User|string */
+       protected $target;
+
+       /**
+        * @var int Block::TYPE_ constant. After the block has been loaded
+        * from the database, this can only be USER, IP or RANGE.
+        */
+       protected $type;
+
+       /** @var User */
+       protected $blocker;
+
+       /** @var bool */
+       protected $isSitewide = true;
+
+       # TYPE constants
+       const TYPE_USER = 1;
+       const TYPE_IP = 2;
+       const TYPE_RANGE = 3;
+       const TYPE_AUTO = 4;
+       const TYPE_ID = 5;
+
+       /**
+        * Create a new block with specified parameters on a user, IP or IP range.
+        *
+        * @param array $options Parameters of the block:
+        *     address string|User  Target user name, User object, IP address or IP range
+        *     by int               User ID of the blocker
+        *     reason string        Reason of the block
+        *     timestamp string     The time at which the block comes into effect
+        *     byText string        Username of the blocker (for foreign users)
+        */
+       function __construct( $options = [] ) {
+               $defaults = [
+                       'address'         => '',
+                       'by'              => null,
+                       'reason'          => '',
+                       'timestamp'       => '',
+                       'byText'          => '',
+               ];
+
+               $options += $defaults;
+
+               $this->setTarget( $options['address'] );
+
+               if ( $options['by'] ) {
+                       # Local user
+                       $this->setBlocker( User::newFromId( $options['by'] ) );
+               } else {
+                       # Foreign user
+                       $this->setBlocker( $options['byText'] );
+               }
+
+               $this->setReason( $options['reason'] );
+               $this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
+       }
+
+       /**
+        * Get the user id of the blocking sysop
+        *
+        * @return int (0 for foreign users)
+        */
+       public function getBy() {
+               return $this->getBlocker()->getId();
+       }
+
+       /**
+        * Get the username of the blocking sysop
+        *
+        * @return string
+        */
+       public function getByName() {
+               return $this->getBlocker()->getName();
+       }
+
+       /**
+        * Get the block ID
+        * @return int|null
+        */
+       public function getId() {
+               return null;
+       }
+
+       /**
+        * Get the reason given for creating the block
+        *
+        * @since 1.33
+        * @return string
+        */
+       public function getReason() {
+               return $this->mReason;
+       }
+
+       /**
+        * Set the reason for creating the block
+        *
+        * @since 1.33
+        * @param string $reason
+        */
+       public function setReason( $reason ) {
+               $this->mReason = $reason;
+       }
+
+       /**
+        * Get whether the block hides the target's username
+        *
+        * @since 1.33
+        * @return bool The block hides the username
+        */
+       public function getHideName() {
+               return $this->mHideName;
+       }
+
+       /**
+        * Set whether ths block hides the target's username
+        *
+        * @since 1.33
+        * @param bool $hideName The block hides the username
+        */
+       public function setHideName( $hideName ) {
+               $this->mHideName = $hideName;
+       }
+
+       /**
+        * Indicates that the block is a sitewide block. This means the user is
+        * prohibited from editing any page on the site (other than their own talk
+        * page).
+        *
+        * @since 1.33
+        * @param null|bool $x
+        * @return bool
+        */
+       public function isSitewide( $x = null ) {
+               return wfSetVar( $this->isSitewide, $x );
+       }
+
+       /**
+        * Get or set the flag indicating whether this block blocks the target from
+        * creating an account. (Note that the flag may be overridden depending on
+        * global configs.)
+        *
+        * @since 1.33
+        * @param null|bool $x Value to set (if null, just get the property value)
+        * @return bool Value of the property
+        */
+       public function isCreateAccountBlocked( $x = null ) {
+               return wfSetVar( $this->blockCreateAccount, $x );
+       }
+
+       /**
+        * Get or set the flag indicating whether this block blocks the target from
+        * sending emails. (Note that the flag may be overridden depending on
+        * global configs.)
+        *
+        * @since 1.33
+        * @param null|bool $x Value to set (if null, just get the property value)
+        * @return bool Value of the property
+        */
+       public function isEmailBlocked( $x = null ) {
+               return wfSetVar( $this->mBlockEmail, $x );
+       }
+
+       /**
+        * Get or set the flag indicating whether this block blocks the target from
+        * editing their own user talk page. (Note that the flag may be overridden
+        * depending on global configs.)
+        *
+        * @since 1.33
+        * @param null|bool $x Value to set (if null, just get the property value)
+        * @return bool Value of the property
+        */
+       public function isUsertalkEditAllowed( $x = null ) {
+               return wfSetVar( $this->allowUsertalk, $x );
+       }
+
+       /**
+        * Determine whether the Block prevents a given right. A right
+        * may be blacklisted or whitelisted, or determined from a
+        * property on the Block object. For certain rights, the property
+        * may be overridden according to global configs.
+        *
+        * @since 1.33
+        * @param string $right Right to check
+        * @return bool|null null if unrecognized right or unset property
+        */
+       public function appliesToRight( $right ) {
+               $config = RequestContext::getMain()->getConfig();
+               $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
+
+               $res = null;
+               switch ( $right ) {
+                       case 'edit':
+                               // TODO: fix this case to return proper value
+                               $res = true;
+                               break;
+                       case 'createaccount':
+                               $res = $this->isCreateAccountBlocked();
+                               break;
+                       case 'sendemail':
+                               $res = $this->isEmailBlocked();
+                               break;
+                       case 'upload':
+                               // Until T6995 is completed
+                               $res = $this->isSitewide();
+                               break;
+                       case 'read':
+                               $res = false;
+                               break;
+                       case 'purge':
+                               $res = false;
+                               break;
+               }
+               if ( !$res && $blockDisablesLogin ) {
+                       // If a block would disable login, then it should
+                       // prevent any right that all users cannot do
+                       $anon = new User;
+                       $res = $anon->isAllowed( $right ) ? $res : true;
+               }
+
+               return $res;
+       }
+
+       /**
+        * Get/set whether the Block prevents a given action
+        *
+        * @deprecated since 1.33, use appliesToRight to determine block
+        *  behaviour, and specific methods to get/set properties
+        * @param string $action Action to check
+        * @param bool|null $x Value for set, or null to just get value
+        * @return bool|null Null for unrecognized rights.
+        */
+       public function prevents( $action, $x = null ) {
+               $config = RequestContext::getMain()->getConfig();
+               $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
+               $blockAllowsUTEdit = $config->get( 'BlockAllowsUTEdit' );
+
+               $res = null;
+               switch ( $action ) {
+                       case 'edit':
+                               # For now... <evil laugh>
+                               $res = true;
+                               break;
+                       case 'createaccount':
+                               $res = wfSetVar( $this->blockCreateAccount, $x );
+                               break;
+                       case 'sendemail':
+                               $res = wfSetVar( $this->mBlockEmail, $x );
+                               break;
+                       case 'upload':
+                               // Until T6995 is completed
+                               $res = $this->isSitewide();
+                               break;
+                       case 'editownusertalk':
+                               // NOTE: this check is not reliable on partial blocks
+                               // since partially blocked users are always allowed to edit
+                               // their own talk page unless a restriction exists on the
+                               // page or User_talk: namespace
+                               wfSetVar( $this->allowUsertalk, $x === null ? null : !$x );
+                               $res = !$this->isUsertalkEditAllowed();
+
+                               // edit own user talk can be disabled by config
+                               if ( !$blockAllowsUTEdit ) {
+                                       $res = true;
+                               }
+                               break;
+                       case 'read':
+                               $res = false;
+                               break;
+                       case 'purge':
+                               $res = false;
+                               break;
+               }
+               if ( !$res && $blockDisablesLogin ) {
+                       // If a block would disable login, then it should
+                       // prevent any action that all users cannot do
+                       $anon = new User;
+                       $res = $anon->isAllowed( $action ) ? $res : true;
+               }
+
+               return $res;
+       }
+
+       /**
+        * From an existing Block, get the target and the type of target.
+        * Note that, except for null, it is always safe to treat the target
+        * as a string; for User objects this will return User::__toString()
+        * which in turn gives User::getName().
+        *
+        * @param string|int|User|null $target
+        * @return array [ User|String|null, Block::TYPE_ constant|null ]
+        */
+       public static function parseTarget( $target ) {
+               # We may have been through this before
+               if ( $target instanceof User ) {
+                       if ( IP::isValid( $target->getName() ) ) {
+                               return [ $target, self::TYPE_IP ];
+                       } else {
+                               return [ $target, self::TYPE_USER ];
+                       }
+               } elseif ( $target === null ) {
+                       return [ null, null ];
+               }
+
+               $target = trim( $target );
+
+               if ( IP::isValid( $target ) ) {
+                       # We can still create a User if it's an IP address, but we need to turn
+                       # off validation checking (which would exclude IP addresses)
+                       return [
+                               User::newFromName( IP::sanitizeIP( $target ), false ),
+                               self::TYPE_IP
+                       ];
+
+               } elseif ( IP::isValidRange( $target ) ) {
+                       # Can't create a User from an IP range
+                       return [ IP::sanitizeRange( $target ), self::TYPE_RANGE ];
+               }
+
+               # Consider the possibility that this is not a username at all
+               # but actually an old subpage (T31797)
+               if ( strpos( $target, '/' ) !== false ) {
+                       # An old subpage, drill down to the user behind it
+                       $target = explode( '/', $target )[0];
+               }
+
+               $userObj = User::newFromName( $target );
+               if ( $userObj instanceof User ) {
+                       # Note that since numbers are valid usernames, a $target of "12345" will be
+                       # considered a User.  If you want to pass a block ID, prepend a hash "#12345",
+                       # since hash characters are not valid in usernames or titles generally.
+                       return [ $userObj, self::TYPE_USER ];
+
+               } elseif ( preg_match( '/^#\d+$/', $target ) ) {
+                       # Autoblock reference in the form "#12345"
+                       return [ substr( $target, 1 ), self::TYPE_AUTO ];
+
+               } else {
+                       return [ null, null ];
+               }
+       }
+
+       /**
+        * Get the type of target for this particular block.
+        * @return int Block::TYPE_ constant, will never be TYPE_ID
+        */
+       public function getType() {
+               return $this->type;
+       }
+
+       /**
+        * Get the target and target type for this particular Block.  Note that for autoblocks,
+        * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
+        * in this situation.
+        * @return array [ User|String, Block::TYPE_ constant ]
+        * @todo FIXME: This should be an integral part of the Block member variables
+        */
+       public function getTargetAndType() {
+               return [ $this->getTarget(), $this->getType() ];
+       }
+
+       /**
+        * Get the target for this particular Block.  Note that for autoblocks,
+        * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
+        * in this situation.
+        * @return User|string
+        */
+       public function getTarget() {
+               return $this->target;
+       }
+
+       /**
+        * Get the block expiry time
+        *
+        * @since 1.19
+        * @return string
+        */
+       public function getExpiry() {
+               return $this->mExpiry;
+       }
+
+       /**
+        * Set the block expiry time
+        *
+        * @since 1.33
+        * @param string $expiry
+        */
+       public function setExpiry( $expiry ) {
+               $this->mExpiry = $expiry;
+       }
+
+       /**
+        * Get the timestamp indicating when the block was created
+        *
+        * @since 1.33
+        * @return string
+        */
+       public function getTimestamp() {
+               return $this->mTimestamp;
+       }
+
+       /**
+        * Set the timestamp indicating when the block was created
+        *
+        * @since 1.33
+        * @param string $timestamp
+        */
+       public function setTimestamp( $timestamp ) {
+               $this->mTimestamp = $timestamp;
+       }
+
+       /**
+        * Set the target for this block, and update $this->type accordingly
+        * @param mixed $target
+        */
+       public function setTarget( $target ) {
+               list( $this->target, $this->type ) = static::parseTarget( $target );
+       }
+
+       /**
+        * Get the user who implemented this block
+        * @return User User object. May name a foreign user.
+        */
+       public function getBlocker() {
+               return $this->blocker;
+       }
+
+       /**
+        * Set the user who implemented (or will implement) this block
+        * @param User|string $user Local User object or username string
+        */
+       public function setBlocker( $user ) {
+               if ( is_string( $user ) ) {
+                       $user = User::newFromName( $user, false );
+               }
+
+               if ( $user->isAnon() && User::isUsableName( $user->getName() ) ) {
+                       throw new InvalidArgumentException(
+                               'Blocker must be a local user or a name that cannot be a local user'
+                       );
+               }
+
+               $this->blocker = $user;
+       }
+
+       /**
+        * Get the key and parameters for the corresponding error message.
+        *
+        * @since 1.22
+        * @param IContextSource $context
+        * @return array
+        */
+       abstract public function getPermissionsError( IContextSource $context );
+
+       /**
+        * Get block information used in different block error messages
+        *
+        * @since 1.33
+        * @param IContextSource $context
+        * @return array
+        */
+       public function getBlockErrorParams( IContextSource $context ) {
+               $blocker = $this->getBlocker();
+               if ( $blocker instanceof User ) { // local user
+                       $blockerUserpage = $blocker->getUserPage();
+                       $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
+               } else { // foreign user
+                       $link = $blocker;
+               }
+
+               $reason = $this->getReason();
+               if ( $reason == '' ) {
+                       $reason = $context->msg( 'blockednoreason' )->text();
+               }
+
+               /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
+                * This could be a username, an IP range, or a single IP. */
+               $intended = $this->getTarget();
+               $lang = $context->getLanguage();
+
+               return [
+                       $link,
+                       $reason,
+                       $context->getRequest()->getIP(),
+                       $this->getByName(),
+                       // TODO: SystemBlock replaces this with the system block type. Clean up
+                       // error params so that this is not necessary.
+                       $this->getId(),
+                       $lang->formatExpiry( $this->getExpiry() ),
+                       (string)$intended,
+                       $lang->userTimeAndDate( $this->getTimestamp(), $context->getUser() ),
+               ];
+       }
+
+       /**
+        * Determine whether the block allows the user to edit their own
+        * user talk page. This is done separately from Block::appliesToRight
+        * because there is no right for editing one's own user talk page
+        * and because the user's talk page needs to be passed into the
+        * Block object, which is unaware of the user.
+        *
+        * The ipb_allow_usertalk flag (which corresponds to the property
+        * allowUsertalk) is used on sitewide blocks and partial blocks
+        * that contain a namespace restriction on the user talk namespace,
+        * but do not contain a page restriction on the user's talk page.
+        * For all other (i.e. most) partial blocks, the flag is ignored,
+        * and the user can always edit their user talk page unless there
+        * is a page restriction on their user talk page, in which case
+        * they can never edit it. (Ideally the flag would be stored as
+        * null in these cases, but the database field isn't nullable.)
+        *
+        * This method does not validate that the passed in talk page belongs to the
+        * block target since the target (an IP) might not be the same as the user's
+        * talk page (if they are logged in).
+        *
+        * @since 1.33
+        * @param Title|null $usertalk The user's user talk page. If null,
+        *  and if the target is a User, the target's userpage is used
+        * @return bool The user can edit their talk page
+        */
+       public function appliesToUsertalk( Title $usertalk = null ) {
+               if ( !$usertalk ) {
+                       if ( $this->target instanceof User ) {
+                               $usertalk = $this->target->getTalkPage();
+                       } else {
+                               throw new InvalidArgumentException(
+                                       '$usertalk must be provided if block target is not a user/IP'
+                               );
+                       }
+               }
+
+               if ( $usertalk->getNamespace() !== NS_USER_TALK ) {
+                       throw new InvalidArgumentException(
+                               '$usertalk must be a user talk page'
+                       );
+               }
+
+               if ( !$this->isSitewide() ) {
+                       if ( $this->appliesToPage( $usertalk->getArticleID() ) ) {
+                               return true;
+                       }
+                       if ( !$this->appliesToNamespace( NS_USER_TALK ) ) {
+                               return false;
+                       }
+               }
+
+               // This is a type of block which uses the ipb_allow_usertalk
+               // flag. The flag can still be overridden by global configs.
+               $config = RequestContext::getMain()->getConfig();
+               if ( !$config->get( 'BlockAllowsUTEdit' ) ) {
+                       return true;
+               }
+               return !$this->isUsertalkEditAllowed();
+       }
+
+       /**
+        * Checks if a block applies to a particular title
+        *
+        * This check does not consider whether `$this->isUsertalkEditAllowed`
+        * returns false, as the identity of the user making the hypothetical edit
+        * isn't known here (particularly in the case of IP hardblocks, range
+        * blocks, and auto-blocks).
+        *
+        * @param Title $title
+        * @return bool
+        */
+       public function appliesToTitle( Title $title ) {
+               return $this->isSitewide();
+       }
+
+       /**
+        * Checks if a block applies to a particular namespace
+        *
+        * @since 1.33
+        *
+        * @param int $ns
+        * @return bool
+        */
+       public function appliesToNamespace( $ns ) {
+               return $this->isSitewide();
+       }
+
+       /**
+        * Checks if a block applies to a particular page
+        *
+        * This check does not consider whether `$this->isUsertalkEditAllowed`
+        * returns false, as the identity of the user making the hypothetical edit
+        * isn't known here (particularly in the case of IP hardblocks, range
+        * blocks, and auto-blocks).
+        *
+        * @since 1.33
+        *
+        * @param int $pageId
+        * @return bool
+        */
+       public function appliesToPage( $pageId ) {
+               return $this->isSitewide();
+       }
+
+       /**
+        * Check if the block should be tracked with a cookie.
+        *
+        * @since 1.33
+        * @param bool $isAnon The user is logged out
+        * @return bool The block should be tracked with a cookie
+        */
+       public function shouldTrackWithCookie( $isAnon ) {
+               return false;
+       }
+
+       /**
+        * Check if the block prevents a user from resetting their password
+        *
+        * @since 1.33
+        * @return bool The block blocks password reset
+        */
+       public function appliesToPasswordReset() {
+               return $this->isCreateAccountBlocked();
+       }
+
 }
index 3ef35d7..ba4c569 100644 (file)
@@ -22,10 +22,10 @@ namespace MediaWiki\Block;
 
 use Block;
 use IP;
+use MediaWiki\User\UserIdentity;
 use User;
 use WebRequest;
 use Wikimedia\IPSet;
-use MediaWiki\User\UserIdentity;
 
 /**
  * A service class for checking blocks.
@@ -139,22 +139,25 @@ class BlockManager {
                $block = Block::newFromTarget( $user, $ip, !$fromReplica );
 
                // Cookie blocking
-               if ( !$block instanceof Block ) {
+               if ( !$block instanceof AbstractBlock ) {
                        $block = $this->getBlockFromCookieValue( $user, $request );
                }
 
                // Proxy blocking
-               if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $this->proxyWhitelist ) ) {
+               if ( !$block instanceof AbstractBlock
+                       && $ip !== null
+                       && !in_array( $ip, $this->proxyWhitelist )
+               ) {
                        // Local list
                        if ( $this->isLocallyBlockedProxy( $ip ) ) {
-                               $block = new Block( [
+                               $block = new SystemBlock( [
                                        'byText' => wfMessage( 'proxyblocker' )->text(),
                                        'reason' => wfMessage( 'proxyblockreason' )->plain(),
                                        'address' => $ip,
                                        'systemBlock' => 'proxy',
                                ] );
                        } elseif ( $isAnon && $this->isDnsBlacklisted( $ip ) ) {
-                               $block = new Block( [
+                               $block = new SystemBlock( [
                                        'byText' => wfMessage( 'sorbs' )->text(),
                                        'reason' => wfMessage( 'sorbsreason' )->plain(),
                                        'address' => $ip,
@@ -164,7 +167,7 @@ class BlockManager {
                }
 
                // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
-               if ( !$block instanceof Block
+               if ( !$block instanceof AbstractBlock
                        && $this->applyIpBlocksToXff
                        && $ip !== null
                        && !in_array( $ip, $this->proxyWhitelist )
@@ -176,19 +179,19 @@ class BlockManager {
                        $xffblocks = Block::getBlocksForIPList( $xff, $isAnon, !$fromReplica );
                        // TODO: remove dependency on Block
                        $block = Block::chooseBlock( $xffblocks, $xff );
-                       if ( $block instanceof Block ) {
+                       if ( $block instanceof AbstractBlock ) {
                                # Mangle the reason to alert the user that the block
                                # originated from matching the X-Forwarded-For header.
                                $block->setReason( wfMessage( 'xffblockreason', $block->getReason() )->plain() );
                        }
                }
 
-               if ( !$block instanceof Block
+               if ( !$block instanceof AbstractBlock
                        && $ip !== null
                        && $isAnon
                        && IP::isInRanges( $ip, $this->softBlockRanges )
                ) {
-                       $block = new Block( [
+                       $block = new SystemBlock( [
                                'address' => $ip,
                                'byText' => 'MediaWiki default',
                                'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
diff --git a/includes/block/SystemBlock.php b/includes/block/SystemBlock.php
new file mode 100644 (file)
index 0000000..2a8c663
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Class for temporary blocks created on enforcement.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Block;
+
+use IContextSource;
+
+/**
+ * System blocks are temporary blocks that are created on enforcement (e.g.
+ * from IP blacklists) and are not saved to the database. The target of a
+ * system block is an IP address. System blocks do not give rise to
+ * autoblocks and are not tracked with cookies.
+ *
+ * @since 1.34
+ */
+class SystemBlock extends AbstractBlock {
+       /** @var string|null */
+       private $systemBlockType;
+
+       /**
+        * Create a new block with specified parameters on a user, IP or IP range.
+        *
+        * @param array $options Parameters of the block:
+        *     systemBlock string   Indicate that this block is automatically
+        *                          created by MediaWiki rather than being stored
+        *                          in the database. Value is a string to return
+        *                          from self::getSystemBlockType().
+        */
+       function __construct( $options = [] ) {
+               parent::__construct( $options );
+
+               $defaults = [
+                       'systemBlock' => null,
+               ];
+
+               $options += $defaults;
+
+               $this->systemBlockType = $options['systemBlock'];
+       }
+
+       /**
+        * Get the system block type, if any. A SystemBlock can have the following types:
+        * - 'proxy': the IP is blacklisted in $wgProxyList
+        * - 'dnsbl': the IP is associated with a blacklisted domain in $wgDnsBlacklistUrls
+        * - 'wgSoftBlockRanges': the IP is covered by $wgSoftBlockRanges
+        * - 'global-block': for backwards compatability with the UserIsBlockedGlobally hook
+        *
+        * @since 1.29
+        * @return string|null
+        */
+       public function getSystemBlockType() {
+               return $this->systemBlockType;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function getPermissionsError( IContextSource $context ) {
+               $params = $this->getBlockErrorParams( $context );
+               // TODO: Clean up error messages params so we don't have to do this
+               $params[ 4 ] = $this->getSystemBlockType();
+
+               $msg = 'systemblockedtext';
+
+               array_unshift( $params, $msg );
+
+               return $params;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function appliesToPasswordReset() {
+               switch ( $this->getSystemBlockType() ) {
+                       case null:
+                       case 'global-block':
+                               return $this->isCreateAccountBlocked();
+                       case 'proxy':
+                               return true;
+                       case 'dnsbl':
+                       case 'wgSoftBlockRanges':
+                               return false;
+                       default:
+                               return true;
+               }
+       }
+
+}
index 9d19f8b..9746c2b 100644 (file)
@@ -18,6 +18,8 @@
  * @file
  */
 
+use MediaWiki\Block\AbstractBlock;
+
 /**
  * Show an error when the user tries to do something whilst blocked.
  *
@@ -25,7 +27,7 @@
  * @ingroup Exception
  */
 class UserBlockedError extends ErrorPageError {
-       public function __construct( Block $block ) {
+       public function __construct( AbstractBlock $block ) {
                // @todo FIXME: Implement a more proper way to get context here.
                $params = $block->getPermissionsError( RequestContext::getMain() );
                parent::__construct( 'blockedtitle', array_shift( $params ), $params );
index 2cea712..2f6deb5 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  */
 
+use MediaWiki\Block\AbstractBlock;
+use MediaWiki\Block\SystemBlock;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Session\SessionManager;
 use MediaWiki\Session\Token;
@@ -278,7 +280,7 @@ class User implements IDBAccessObject, UserIdentity {
        protected $mImplicitGroups;
        /** @var array */
        protected $mFormerGroups;
-       /** @var Block */
+       /** @var AbstractBlock */
        protected $mGlobalBlock;
        /** @var bool */
        protected $mLocked;
@@ -290,13 +292,13 @@ class User implements IDBAccessObject, UserIdentity {
        /** @var WebRequest */
        private $mRequest;
 
-       /** @var Block */
+       /** @var AbstractBlock */
        public $mBlock;
 
        /** @var bool */
        protected $mAllowUsertalk;
 
-       /** @var Block */
+       /** @var AbstractBlock */
        private $mBlockedFromCreateAccount = false;
 
        /** @var int User::READ_* constant bitfield used to load data */
@@ -1848,7 +1850,7 @@ class User implements IDBAccessObject, UserIdentity {
                        $fromReplica
                );
 
-               if ( $block instanceof Block ) {
+               if ( $block instanceof AbstractBlock ) {
                        wfDebug( __METHOD__ . ": Found block.\n" );
                        $this->mBlock = $block;
                        $this->mBlockedby = $block->getByName();
@@ -2162,7 +2164,7 @@ class User implements IDBAccessObject, UserIdentity {
         * @return bool True if blocked, false otherwise
         */
        public function isBlocked( $fromReplica = true ) {
-               return $this->getBlock( $fromReplica ) instanceof Block &&
+               return $this->getBlock( $fromReplica ) instanceof AbstractBlock &&
                        $this->getBlock()->appliesToRight( 'edit' );
        }
 
@@ -2170,11 +2172,11 @@ class User implements IDBAccessObject, UserIdentity {
         * Get the block affecting the user, or null if the user is not blocked
         *
         * @param bool $fromReplica Whether to check the replica DB instead of the master
-        * @return Block|null
+        * @return AbstractBlock|null
         */
        public function getBlock( $fromReplica = true ) {
                $this->getBlockedStatus( $fromReplica );
-               return $this->mBlock instanceof Block ? $this->mBlock : null;
+               return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
        }
 
        /**
@@ -2230,7 +2232,7 @@ class User implements IDBAccessObject, UserIdentity {
         * @return bool True if blocked, false otherwise
         */
        public function isBlockedGlobally( $ip = '' ) {
-               return $this->getGlobalBlock( $ip ) instanceof Block;
+               return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
        }
 
        /**
@@ -2239,7 +2241,7 @@ class User implements IDBAccessObject, UserIdentity {
         * This is intended for quick UI checks.
         *
         * @param string $ip IP address, uses current client if none given
-        * @return Block|null Block object if blocked, null otherwise
+        * @return AbstractBlock|null Block object if blocked, null otherwise
         * @throws FatalError
         * @throws MWException
         */
@@ -2261,7 +2263,7 @@ class User implements IDBAccessObject, UserIdentity {
 
                if ( $blocked && $block === null ) {
                        // back-compat: UserIsBlockedGlobally didn't have $block param first
-                       $block = new Block( [
+                       $block = new SystemBlock( [
                                'address' => $ip,
                                'systemBlock' => 'global-block'
                        ] );
@@ -4392,7 +4394,7 @@ class User implements IDBAccessObject, UserIdentity {
 
        /**
         * Get whether the user is explicitly blocked from account creation.
-        * @return bool|Block
+        * @return bool|AbstractBlock
         */
        public function isBlockedFromCreateAccount() {
                $this->getBlockedStatus();
@@ -4406,7 +4408,7 @@ class User implements IDBAccessObject, UserIdentity {
                if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
                        $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
                }
-               return $this->mBlockedFromCreateAccount instanceof Block
+               return $this->mBlockedFromCreateAccount instanceof AbstractBlock
                        && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
                        ? $this->mBlockedFromCreateAccount
                        : false;
index 61e3e7c..dac3b87 100644 (file)
@@ -437,43 +437,6 @@ class BlockTest extends MediaWikiLangTestCase {
                );
        }
 
-       /**
-        * @covers Block::getSystemBlockType
-        * @covers Block::insert
-        * @covers Block::doAutoblock
-        */
-       public function testSystemBlocks() {
-               $user = $this->getUserForBlocking();
-               $this->addBlockForUser( $user );
-
-               $blockOptions = [
-                       'address' => $user->getName(),
-                       'reason' => 'test system block',
-                       'timestamp' => wfTimestampNow(),
-                       'expiry' => $this->db->getInfinity(),
-                       'byText' => 'MediaWiki default',
-                       'systemBlock' => 'test',
-                       'enableAutoblock' => true,
-               ];
-               $block = new Block( $blockOptions );
-
-               $this->assertSame( 'test', $block->getSystemBlockType() );
-
-               try {
-                       $block->insert();
-                       $this->fail( 'Expected exception not thrown' );
-               } catch ( MWException $ex ) {
-                       $this->assertSame( 'Cannot insert a system block into the database', $ex->getMessage() );
-               }
-
-               try {
-                       $block->doAutoblock( '192.0.2.2' );
-                       $this->fail( 'Expected exception not thrown' );
-               } catch ( MWException $ex ) {
-                       $this->assertSame( 'Cannot autoblock from a system block', $ex->getMessage() );
-               }
-       }
-
        /**
         * @covers Block::newFromRow
         */
index a6c00dc..4676fc5 100644 (file)
@@ -10,6 +10,7 @@ use Title;
 use User;
 use MediaWiki\Block\Restriction\NamespaceRestriction;
 use MediaWiki\Block\Restriction\PageRestriction;
+use MediaWiki\Block\SystemBlock;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Permissions\PermissionManager;
 
@@ -1080,19 +1081,18 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                #   $user->mBlock->mExpiry == 'infinity'
 
                $this->user->mBlockedby = $this->user->getName();
-               $this->user->mBlock = new Block( [
+               $this->user->mBlock = new SystemBlock( [
                        'address' => '127.0.8.1',
                        'by' => $this->user->getId(),
                        'reason' => 'no reason given',
                        'timestamp' => $now,
                        'auto' => false,
-                       'expiry' => 10,
                        'systemBlock' => 'test',
                ] );
 
                $errors = [ [ 'systemblockedtext',
                        '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                       'Useruser', 'test', '23:00, 31 December 1969', '127.0.8.1',
+                       'Useruser', 'test', 'infinite', '127.0.8.1',
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
diff --git a/tests/phpunit/includes/SystemBlockTest.php b/tests/phpunit/includes/SystemBlockTest.php
new file mode 100644 (file)
index 0000000..321b25e
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+use MediaWiki\Block\SystemBlock;
+
+/**
+ * @group Blocking
+ * @coversDefaultClass \MediaWiki\Block\SystemBlock
+ */
+class SystemBlockTest extends MediaWikiLangTestCase {
+       /**
+        * @covers ::getSystemBlockType
+        */
+       public function testSystemBlockType() {
+               $block = new SystemBlock( [
+                       'systemBlock' => 'proxy',
+               ] );
+
+               $this->assertSame( 'proxy', $block->getSystemBlockType() );
+       }
+
+}
index e5e265f..1f8011d 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 
 use MediaWiki\Block\Restriction\PageRestriction;
+use MediaWiki\Block\SystemBlock;
 use MediaWiki\MediaWikiServices;
 
 /**
@@ -960,19 +961,17 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                #   $user->mBlock->mExpiry == 'infinity'
 
                $this->user->mBlockedby = $this->user->getName();
-               $this->user->mBlock = new Block( [
+               $this->user->mBlock = new SystemBlock( [
                        'address' => '127.0.8.1',
                        'by' => $this->user->getId(),
                        'reason' => 'no reason given',
                        'timestamp' => $now,
-                       'auto' => false,
-                       'expiry' => 10,
                        'systemBlock' => 'test',
                ] );
 
                $errors = [ [ 'systemblockedtext',
                                '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                               'Useruser', 'test', '23:00, 31 December 1969', '127.0.8.1',
+                               'Useruser', 'test', 'infinite', '127.0.8.1',
                                $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
index f05cfbc..932495a 100644 (file)
@@ -1,43 +1,42 @@
 <?php
 
 use Wikimedia\TestingAccessWrapper;
+use MediaWiki\Block\SystemBlock;
 
 /**
  * @covers ApiBlockInfoTrait
  */
 class ApiBlockInfoTraitTest extends MediaWikiTestCase {
-
-       public function testGetBlockInfo() {
-               $block = new Block();
+       /**
+        * @dataProvider provideGetBlockInfo
+        */
+       public function testGetBlockInfo( $block, $expectedInfo ) {
                $mock = $this->getMockForTrait( ApiBlockInfoTrait::class );
                $info = TestingAccessWrapper::newFromObject( $mock )->getBlockInfo( $block );
-               $subset = [
+               $subset = array_merge( [
                        'blockid' => null,
                        'blockedby' => '',
                        'blockedbyid' => 0,
                        'blockreason' => '',
                        'blockexpiry' => 'infinite',
-                       'blockpartial' => false,
-               ];
+               ], $expectedInfo );
                $this->assertArraySubset( $subset, $info );
        }
 
-       public function testGetBlockInfoPartial() {
-               $mock = $this->getMockForTrait( ApiBlockInfoTrait::class );
-
-               $block = new Block( [
-                       'sitewide' => false,
-               ] );
-               $info = TestingAccessWrapper::newFromObject( $mock )->getBlockInfo( $block );
-               $subset = [
-                       'blockid' => null,
-                       'blockedby' => '',
-                       'blockedbyid' => 0,
-                       'blockreason' => '',
-                       'blockexpiry' => 'infinite',
-                       'blockpartial' => true,
+       public static function provideGetBlockInfo() {
+               return [
+                       'Sitewide block' => [
+                               new Block(),
+                               [ 'blockpartial' => false ],
+                       ],
+                       'Partial block' => [
+                               new Block( [ 'sitewide' => false ] ),
+                               [ 'blockpartial' => true ],
+                       ],
+                       'System block' => [
+                               new SystemBlock( [ 'systemBlock' => 'proxy' ] ),
+                               [ 'systemblocktype' => 'proxy' ]
+                       ],
                ];
-               $this->assertArraySubset( $subset, $info );
        }
-
 }
index e8334d6..ca57c10 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 
 use MediaWiki\Auth\AuthManager;
+use MediaWiki\Block\SystemBlock;
 
 /**
  * @covers PasswordReset
@@ -102,26 +103,21 @@ class PasswordResetTest extends MediaWikiTestCase {
                                'enableEmail' => true,
                                'allowsAuthenticationDataChange' => true,
                                'canEditPrivate' => true,
-                               'block' => new Block( [ 'systemBlock' => 'proxy' ] ),
+                               'block' => new SystemBlock(
+                                       [ 'systemBlock' => 'proxy' ]
+                               ),
                                'globalBlock' => null,
                                'isAllowed' => false,
                        ],
-                       'globally blocked with account creation disabled' => [
-                               'passwordResetRoutes' => [ 'username' => true ],
-                               'enableEmail' => true,
-                               'allowsAuthenticationDataChange' => true,
-                               'canEditPrivate' => true,
-                               'block' => null,
-                               'globalBlock' => new Block( [ 'systemBlock' => 'global-block', 'createAccount' => true ] ),
-                               'isAllowed' => false,
-                       ],
                        'globally blocked with account creation not disabled' => [
                                'passwordResetRoutes' => [ 'username' => true ],
                                'enableEmail' => true,
                                'allowsAuthenticationDataChange' => true,
                                'canEditPrivate' => true,
                                'block' => null,
-                               'globalBlock' => new Block( [ 'systemBlock' => 'global-block', 'createAccount' => false ] ),
+                               'globalBlock' => new SystemBlock(
+                                       [ 'systemBlock' => 'global-block' ]
+                               ),
                                'isAllowed' => true,
                        ],
                        'blocked via wgSoftBlockRanges' => [
@@ -129,7 +125,9 @@ class PasswordResetTest extends MediaWikiTestCase {
                                'enableEmail' => true,
                                'allowsAuthenticationDataChange' => true,
                                'canEditPrivate' => true,
-                               'block' => new Block( [ 'systemBlock' => 'wgSoftBlockRanges', 'anonOnly' => true ] ),
+                               'block' => new SystemBlock(
+                                       [ 'systemBlock' => 'wgSoftBlockRanges', 'anonOnly' => true ]
+                               ),
                                'globalBlock' => null,
                                'isAllowed' => true,
                        ],
@@ -138,7 +136,7 @@ class PasswordResetTest extends MediaWikiTestCase {
                                'enableEmail' => true,
                                'allowsAuthenticationDataChange' => true,
                                'canEditPrivate' => true,
-                               'block' => new Block( [ 'systemBlock' => 'unknown' ] ),
+                               'block' => new SystemBlock( [ 'systemBlock' => 'unknown' ] ),
                                'globalBlock' => null,
                                'isAllowed' => false,
                        ],
index 48c8a95..aeeae11 100644 (file)
@@ -5,6 +5,7 @@ define( 'NS_UNITTEST_TALK', 5601 );
 
 use MediaWiki\Block\Restriction\PageRestriction;
 use MediaWiki\Block\Restriction\NamespaceRestriction;
+use MediaWiki\Block\SystemBlock;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\User\UserIdentityValue;
 use Wikimedia\TestingAccessWrapper;
@@ -804,7 +805,7 @@ class UserTest extends MediaWikiTestCase {
                $request->setIP( '10.20.30.40' );
                $setSessionUser( $wgUser, $request );
                $block = $wgUser->getBlock();
-               $this->assertInstanceOf( Block::class, $block );
+               $this->assertInstanceOf( SystemBlock::class, $block );
                $this->assertSame( 'wgSoftBlockRanges', $block->getSystemBlockType() );
 
                // Make sure the block is really soft